using System;
using System.Text;
using System.Data;
using System.Collections;
using System.Collections.Generic;

using Framework;
using System.Threading;
using System.Web;
using SchemaAudit;
using System.Threading.Tasks;

namespace SchemaDeploy
{
    //Table-Row Class (Customisable half)
    public partial class CBackup
    {
        #region Constants
        //Join Expressions
        //private static string JOIN_SAMPLE = String.Concat(TABLE_NAME, " LEFT OUTER JOIN ", CSample.TABLE_NAME, " ON BackupSampleId=SampleId");
        #endregion

        #region Constructors (Public)
        //Default Connection String
        public CBackup() : base() {}
        public CBackup(int backupId) : base(backupId) {}
        
        //Alternative Connection String
        public CBackup(CDataSrc dataSrc) : base(dataSrc) {}
        
        public CBackup(CDataSrc dataSrc, int backupId) : base(dataSrc, backupId) {}
        
        //Transactional (shares an open connection)
        protected internal CBackup(CDataSrc dataSrc, int backupId, IDbTransaction txOrNull) : base(dataSrc, backupId, txOrNull) {}        
        #endregion

        #region Default Values
        protected override void InitValues_Custom()
        {
            //Custom default values (e.g. DateCreated column)
            //Member variables (e.g. for child collections)
            _backupItems = new CBackupItemList();

            _backupCreated = DateTime.Now;
        }
        #endregion
        
        #region Default Connection String
        protected override CDataSrc DefaultDataSrc()  { return CDataSrc.Default;  }
        #endregion

        #region Members
        //Foreign Keys

        //Child Collections  
        [NonSerialized()] private CBackupItemList _backupItems;
        [NonSerialized()] private CBackupItemList _backupItemsAll;

        //Xml Data (as high-level objects)

        #endregion

        #region Properties - Relationships
        //Relationships - Foriegn Keys (e.g parent)
        public CInstance Instance { get { return CInstance.Cache.GetById(this.BackupInstanceId); } }


        //Relationships - Collections (e.g. children)
        public CBackupItemList BackupItems
        {
            get
            {
                if (_backupItems == null)
                {
                    lock (this)
                    {
                        if (_backupItems == null)
                        {
                            _backupItems = new CBackupItem(DataSrc).SelectByBackupId(this.BackupId);
                            _backupItems.Backup = this;
                        }
                    }
                }
                return _backupItems;
            }
            set
            {
                _backupItems = value;
                if (null != value)
                    _backupItems.Backup = this;
            }
        }
        protected CBackupItemList BackupItems_(IDbTransaction tx)   //Only used for cascade deletes
        {
            return new CBackupItem(DataSrc).SelectByBackupId(this.BackupId, tx);
        }
        public int BackupItemsCount()
        {
            if (null != _backupItems) return _backupItems.Count;
            return new CBackupItem(DataSrc).SelectCountByBackupId(this.BackupId);
        }


        public CBackupItemList BackupItemsAll
        {
            get
            {
                if (_backupItemsAll == null)
                {
                    lock (this)
                    {
                        if (_backupItemsAll == null)
                        {
                            var temp = new CBackupItemList(BackupItems);
                            var next = this.Next();
                            var total = _backupTableCount;
                            if (int.MinValue == total)
                                total = int.MaxValue;
                            while (temp.Count < total && null != next)
                            {
                                foreach (var i in next.BackupItems)
                                    if (temp.GetByTableName_(i.ItemTableName) == null)
                                        temp.Add(i);
                                next = next.Next();
                            }
                            _backupItemsAll = temp;
                        }
                    }
                }
                return _backupItemsAll;
            }
            set
            {
                _backupItemsAll = value;
            }
        }
        #endregion

        #region Properties - Customisation
        //Derived/ReadOnly (e.g. xml classes, presentation logic)

        public string AppAndName   { get { return Instance.AppAndName; } }
        public string InstanceName { get { return Instance.NameAndSuffix; } }
        public string CountSummary { get { return CUtilities.CountSummary(BackupItemsCount(), "tbl", "none"); } }


        public string TotalSize_ { get { return long.MinValue == TotalSize ? string.Empty : CUtilities.FileSize(TotalSize); } }
        #endregion

       #region Save/Delete Overrides
        public override void Delete(IDbTransaction txOrNull)
        {
            //Use a transaction if none supplied 
            if (txOrNull == null)
            {
                BulkDelete(this);
                return;
            }

            //Cascade-Delete (all child collections) 
            this.BackupItems_(txOrNull).DeleteAll(txOrNull);

            //Normal Delete 
            base.Delete(txOrNull);
        }

        #endregion

        #region Custom Database Queries
        //For Stored Procs can use: MakeList (matching schema), or DataSrc.ExecuteDataset (reports etc)
        //For Dynamic sql, can use: SelectSum, SelectDistinct, SelectCount, SelectWhere (inherited methods)
        //                see also: SelectBy[FK], Search and Count (auto-generated sample queries)

        public CBackup Last(int instanceId)
        {
            var pi = new CPagingInfo();
            pi.PageSize = 1;
            var list = SelectByInstanceId(pi, instanceId);
            return list.Count > 0 ? list[0] : null;
        }
        public CBackup Prev()
        {
            var pi = new CPagingInfo();
            pi.PageSize = 1;
            pi.SortByColumn = "BackupId";
            pi.Descending = true;

            var w = new CCriteriaList("BackupInstanceId", BackupInstanceId);
            w.Add("BackupId", ESign.LessThan, BackupId);
            var list = SelectWhere(pi, w);
            return list.Count > 0 ? list[0] : null;
        }
        public CBackup Next()
        {
            var pi = new CPagingInfo();
            pi.PageSize = 1;
            pi.SortByColumn = "BackupId";
            pi.Descending = false;

            var w = new CCriteriaList("BackupInstanceId", BackupInstanceId);
            w.Add("BackupId", ESign.GreaterThan, BackupId);
            var list = SelectWhere(pi, w);
            return list.Count > 0 ? list[0] : null;
        }
        #endregion

        #region Searching (Optional)
        //Dynamic search methods: (overload as required for common search patterns, cascade the BuildWhere overloads)
        //   Public  x5 - Simple, Paged, Transactional, Count, and Dataset
        //   Private x1 - BuildWhere
        //See also in-memory search options in list class, such as GetBy[FK] and Search

        //Simple
        public CBackupList SelectSearch(string nameOrId /**/)  {   return SelectWhere(BuildWhere(nameOrId /**/));  } //, JOIN_OR_VIEW); }

        //Paged
        public CBackupList SelectSearch(CPagingInfo pi, string nameOrId /*,  */)
        {
            //pi.TableName = JOIN_OR_VIEW
            return SelectWhere(pi, BuildWhere(nameOrId /**/));
        }

        //Transactional
        public CBackupList SelectSearch(string nameOrId, /* , */ IDbTransaction tx)   {   return SelectWhere(BuildWhere(nameOrId /*,  */), tx);   }   //, JOIN_OR_VIEW, tx); }

        //Dataset (e.g. ExportToCsv)
        public DataSet SelectSearch_Dataset(string nameOrId /*, */)   {   return SelectWhere_Dataset(BuildWhere(nameOrId /*,  */));   }   //, JOIN_OR_VIEW); }

        //Count
        public int SelectCount(string nameOrId /*, */)   {   return SelectCount(BuildWhere(nameOrId /*,  */));   }   //, JOIN_OR_VIEW); }

        //Filter Logic
        private CCriteriaList BuildWhere(string nameOrId /*,  */)
        {
            CCriteriaList where = new CCriteriaList();  //Defaults to AND logic
            
            //Simple search box UI
            if (!string.IsNullOrEmpty(nameOrId)) 
            {
                //Interpret search string in various ways using OR sub-expression
                CCriteriaGroup orExpr = new CCriteriaGroup(EBoolOperator.Or);
                
                //Special case - search by PK
                /* 
                int id = 0;
                if (int.TryParse(nameOrId, out id))
                    orExpr.Add("BackupId", id); 
                */
                
                //Search a range of string columns
                string wildCards = string.Concat("%", nameOrId, "%");

                //Conclude
                if (orExpr.Group.Count > 0)
                    where.Add(orExpr);
            }
            
			//Other search Colums (customise as required)

            return where;
        }
        #endregion


        #region Cloning
        public CBackup Clone(CDataSrc target, IDbTransaction txOrNull) //, int parentId)
        {
            //Shallow copy: Copies the immediate record, excluding autogenerated Pks
            CBackup copy = new CBackup(this, target);

            //Deep Copy - Child Entities: Cloned children must reference their cloned parent
            //copy.SampleParentId = parentId;

            copy.Save(txOrNull);

            //Deep Copy - Parent Entities: Cloned parents also clone their child collections
            //this.Children.Clone(target, txOrNull, copy.BackupId);

            return copy;
        }
        #endregion

        #region ToXml
        protected override void ToXml_Custom(System.Xml.XmlWriter w)
        {
            //Store(w, "Example", this..Example)
        }
        #endregion



        #region Static - Backup Logic

        public static void BackupInstance(CInstance ins, HttpContext c, List<string> exceptSchema, Dictionary<string, Exception> errors, List<string> skipped, DateTime batchDate, int parallel = 3)
        {
            HttpContext.Current = c;

            var lastBackup = ins.LastBackup();
            var lastTables = null != lastBackup ? lastBackup.BackupItems : null;

            var fresh = new CBackup();
            fresh.BackupCreated = batchDate;
            fresh.BackupInstanceId = ins.InstanceId;
            try
            {
                //SqlClient or WebService
                var db = ins.DatabaseDirectOrWeb;
                CTableInfoList tbls = null;
                try
                {
                    tbls = new CTableInfoList(db, false);   //More efficient, but subject to firewalls etc
                }
                catch
                {
                    db = ins.DatabaseViaWeb;
                    tbls = new CTableInfoList(db, false);
                }

                //Summary of todo-tables
                fresh.BackupTableCount = tbls.Count;
                fresh.Save();

                var po = new ParallelOptions() { MaxDegreeOfParallelism = parallel };
                Parallel.ForEach(tbls.Names_, po, tbl => 
                {
                    Exception ex = null;

                    BackupTable(db, tbl, fresh, lastTables, skipped, exceptSchema, c, ref ex);

                    if (null != ex)
                        lock (errors)
                        { errors.Add(tbl, ex); }
                });


                fresh.BackupTableCount = tbls.Count - skipped.Count;
                fresh.Save();

            }
            catch (Exception ex)
            {
                errors.Add(ins.NameAndSuffix, ex);
                fresh.BackupDescription = string.Concat(fresh.BackupDescription, "\r\n", ex.Message);
            }
            finally
            {
                if (skipped.Count > 0)
                    fresh.BackupDescription = string.Concat(fresh.BackupDescription, "Skipped: ", CUtilities.ListToString(skipped));

                foreach (var i in errors)
                    fresh.BackupDescription = string.Concat(fresh.BackupDescription, "\r\n", i.Key, "\t", i.Value.Message);

                fresh.Save();
            }
        }

        private static void BackupTable(CDataSrc db, string table, CBackup fresh, CBackupItemList lastItems, List<string> skipped, List<string> exceptTableSchemas, HttpContext c, ref Exception ex)
        {
            HttpContext.Current = c;

            foreach (var s in exceptTableSchemas)
                if (table.StartsWith(s) || table.StartsWith("[" + s))
                    lock (skipped)
                    {
                        table = table.Replace("[", "").Replace("]", "").Replace(s + ".", "");
                        skipped.Add(table);
                        return;
                    }


            try
            {
                var ds = GetDataset(db, table);

                var bi = new CBackupItem();
                bi.ItemBackupId = fresh.BackupId;
                bi.Dataset = ds;

                //No change => Move previous record forward (effectively sharing it)
                if (null != lastItems)
                {
                    var last = lastItems.GetByTableName_(table);
                    if (null != last && last.ItemDatasetMD5 == bi.ItemDatasetMD5)
                        if (last.MoveTo(fresh.BackupId, bi.ItemRowCount))
                            return;
                }

                bi.Save();
            }
            catch (Exception ex_)
            {
                CAudit_Error.Log(ex_);
                ex = ex_;
            }
        }
        private static DataSet GetDataset(CDataSrc db, string tbl, int maxAttempts = 3)
        {
            try
            {
                var ds = db.SelectAll_Dataset(tbl);
                ds.Tables[0].TableName = tbl;
                return ds;
            }
            catch
            {
                if (maxAttempts == 1)
                    throw;

                Thread.Sleep(5 * 1000);
                return GetDataset(db, tbl, maxAttempts - 1);
            }
        }
        #endregion
    }
}